/*
 * Sylvain BERTRAND <digital.ragnarok@gmail.com>
 * code protected by GNU GPL v3
 */
#include <locale.h>
#include <unistd.h>
#include <errno.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <X11/Xlib.h>
#include <X11/cursorfont.h>
#include <X11/Xcursor/Xcursor.h>
#include <X11/extensions/sync.h>
#include <glib.h>
#include <libxml/xinclude.h>
#include "../nls.h"
#include "../toolkit/signal.h"
#include "../toolkit/display.h"
#include "../toolkit/prop.h"
#include "../toolkit/keyboard.h"
#include "../toolkit/xml.h"
#include "../toolkit/xqueue.h"
#include "misc.h"
#include "wm.h"
#include "debug.h"
#include "window.h"
#include "geom.h"
#include "stacking.h"
#include "client.h"
#include "screen.h"
#include "event.h"
#include "frame.h"
#include "actions.h"
#include "config.h"
#include "place.h"
#include "moveresize.h"
#include "focus.h"
#include "focus_cycle.h"
#include "focus_cycle_indicator.h"
#include "grab.h"
#include "group.h"
#include "keyboard.h"
#include "mouse.h"
gboolean wm_replace = FALSE;
gboolean wm_debug_xinerama = FALSE;
static GMainLoop *main_loop;
static const gchar *wm_locale_msg = NULL;
static enum wm_state state;
static gboolean xsync = FALSE;
static gboolean restart = FALSE;
static gchar *restart_path = NULL;
static Cursor cursors[WM_NUM_CURSORS];
static gint exitcode = 0;
static gboolean being_replaced = FALSE;
static gchar *config_file = NULL;
static gchar *startup_cmd = NULL;
static void signal_handler(gint signal, gpointer data);
static void remove_args(gint *argc, gchar **argv, gint index, gint num);
static void parse_env();
static void parse_args(gint *argc, gchar **argv);
static Cursor load_cursor(const gchar *name, guint fontval);
static void run_startup_cmd(void);

static void exit_with_error(const gchar *msg)
{
	g_message("%s", msg);
	exit(EXIT_FAILURE);
}

gint main(gint argc, gchar **argv)
{
	gchar *program_name;
	t_signal_listen();
	wm_set_state(WM_STATE_STARTING);
	wm_debug_startup();
	if(!(wm_locale_msg = setlocale(LC_MESSAGES, "")))
		g_message("Couldn't set messages locale category from environment.");
	if(!setlocale(LC_ALL, ""))
		g_message("Couldn't set locale from environment.");
	bindtextdomain("lboxwm", LOCALEDIR);
	bind_textdomain_codeset("lboxwm", "UTF-8");
	textdomain("lboxwm");
	if(chdir(g_get_home_dir()) == -1)
		g_message(_("Unable to change to home directory \"%s\": %s"), g_get_home_dir(), g_strerror(errno));
	parse_args(&argc, argv);
	parse_env();
	program_name = g_path_get_basename(argv[0]);
	g_set_prgname(program_name);
	if(!t_display_open(NULL))
		exit_with_error(_("Failed to open the display from the DISPLAY environment variable."));
	main_loop = g_main_loop_new(NULL, FALSE);
	t_signal_add_callback(SIGUSR1, signal_handler, NULL);
	t_signal_add_callback(SIGUSR2, signal_handler, NULL);
	t_signal_add_callback(SIGTERM, signal_handler, NULL);
	t_signal_add_callback(SIGINT, signal_handler, NULL);
	t_signal_add_callback(SIGHUP, signal_handler, NULL);
	t_signal_add_callback(SIGPIPE, signal_handler, NULL);
	t_signal_add_callback(SIGCHLD, signal_handler, NULL);
	t_signal_add_callback(SIGTTIN, signal_handler, NULL);
	t_signal_add_callback(SIGTTOU, signal_handler, NULL);
	if(DefaultVisual(t_display, DefaultScreen(t_display))->class != TrueColor)
		exit_with_error("visual class not TrueColor");
	XSynchronize(t_display, xsync);
	if(!XSupportsLocale())
		g_message(_("X server does not support locale."));
	if(!XSetLocaleModifiers(""))
		g_message(_("Cannot set locale modifiers for the X server."));
	g_setenv("DISPLAY", DisplayString(t_display), TRUE);
	cursors[WM_CURSOR_NONE] = None;
	cursors[WM_CURSOR_POINTER] = load_cursor("left_ptr", XC_left_ptr);
	cursors[WM_CURSOR_BUSYPOINTER] = load_cursor("left_ptr_watch", XC_left_ptr);
	cursors[WM_CURSOR_BUSY] = load_cursor("watch", XC_watch);
	cursors[WM_CURSOR_MOVE] = load_cursor("fleur", XC_fleur);
	cursors[WM_CURSOR_NORTH] = load_cursor("top_side", XC_top_side);
	cursors[WM_CURSOR_NORTHEAST] = load_cursor("top_right_corner", XC_top_right_corner);
	cursors[WM_CURSOR_EAST] = load_cursor("right_side", XC_right_side);
	cursors[WM_CURSOR_SOUTHEAST] = load_cursor("bottom_right_corner", XC_bottom_right_corner);
	cursors[WM_CURSOR_SOUTH] = load_cursor("bottom_side", XC_bottom_side);
	cursors[WM_CURSOR_SOUTHWEST] = load_cursor("bottom_left_corner", XC_bottom_left_corner);
	cursors[WM_CURSOR_WEST] = load_cursor("left_side", XC_left_side);
	cursors[WM_CURSOR_NORTHWEST] = load_cursor("top_left_corner", XC_top_left_corner);
	if(screen_annex()) {
		event_reset_time();
		{
			struct t_xml_inst *i;
			i = t_xml_instance_new();
			actions_startup();
			config_startup(i);
			if((config_file && t_xml_load_file(i, config_file, "lboxwm_config")) || t_xml_load_config_file(i, "lboxwm",
																								"rc.xml", "lboxwm_config")) {
				t_xml_tree_from_root(i);
				t_xml_close(i);
			} else {
				g_message(_("Unable to find a valid config file, using some simple defaults"));
				config_file = NULL;
			}
			if(config_file) {
				gchar *p = g_filename_to_utf8(config_file, -1, NULL, NULL, NULL);
				if(p)
					T_PROP_SETS(DefaultRootWindow(t_display), LBOXWM_CONFIG_FILE, p);
				g_free(p);
			} else
				T_PROP_ERASE(DefaultRootWindow(t_display), LBOXWM_CONFIG_FILE);
			t_xml_instance_unref(i);
		}
		event_startup();
		window_startup();
		focus_startup();
		focus_cycle_startup();
		focus_cycle_indicator_startup();
		screen_startup();
		grab_startup();
		group_startup();
		client_startup();
		moveresize_startup();
		keyboard_startup();
		mouse_startup();
		xqueue_listen();
		window_manage_all();
		wm_set_state(WM_STATE_RUNNING);
		if(startup_cmd)
			run_startup_cmd();
		{
			xmlErrorPtr e = xmlGetLastError();
			if(e) {
				gchar *m;
				m = g_strdup_printf(_
					("One or more XML syntax errors were found while parsing the lboxwm configuration files.  See stdout for more information.  The last error seen was in file \"%s\" line %d, with message: %s"),
					e->file, e->line, e->message);
				g_free(m);
				xmlResetError(e);
			}
		}
		g_main_loop_run(main_loop);
		wm_set_state(WM_STATE_EXITING);
		window_unmanage_all();
		mouse_shutdown();
		keyboard_shutdown();
		moveresize_shutdown();
		client_shutdown();
		group_shutdown();
		grab_shutdown();
		screen_shutdown();
		focus_cycle_indicator_shutdown();
		focus_cycle_shutdown();
		focus_shutdown();
		window_shutdown();
		event_shutdown();
		config_shutdown();
		actions_shutdown();
	}
	XSync(t_display, FALSE);
	t_display_close();
	if(restart) {
		wm_debug_shutdown();
		t_signal_stop();
		if(restart_path != NULL) {
			gint argcp;
			gchar **argvp;
			GError *err = NULL;
			if(g_shell_parse_argv(restart_path, &argcp, &argvp, &err)) {
				execvp(argvp[0], argvp);
				g_strfreev(argvp);
			} else {
				g_message(_("Restart failed to execute new executable \"%s\": %s"), restart_path, err->message);
				g_error_free(err);
			}
		}
		execvp(argv[0], argv);
		execlp(argv[0], program_name, (gchar *)NULL);
	}
	g_free(program_name);
	if(!restart) {
		wm_debug_shutdown();
		t_signal_stop();
	}
	return exitcode;
}

static void signal_handler(gint signal, gpointer data)
{
	(void)data;
	switch (signal) {
	case SIGUSR1:
		wm_debug("Caught signal %d. Restarting.", signal);
		wm_restart();
		break;
	case SIGCHLD:
		while(waitpid(-1, NULL, WNOHANG) > 0);
		break;
	case SIGTTIN:
	case SIGTTOU:
		wm_debug("Caught signal %d. Ignoring.", signal);
		break;
	default:
		wm_debug("Caught signal %d. Exiting.", signal);
		wm_exit(!(signal == SIGTERM || signal == SIGINT));
	}
}

static void print_version(void)
{
	g_print("lboxwm %s\n", PACKAGE_VERSION);
}

static void print_help(void)
{
	g_print(_("Syntax: lboxwm [options]\n"));
	g_print(_("\nOptions:\n"));
	g_print(_("  --help              Display this help and exit\n"));
	g_print(_("  --version           Display the version and exit\n"));
	g_print(_("  --replace           Replace the currently running window manager\n"));
	g_print(_("  --config-file FILE  Specify the path to the config file to use\n"));
	g_print(_("\nDebugging options:\n"));
	g_print(_("  --sync              Run in synchronous mode\n"));
	g_print(_("  --startup CMD       Run CMD after starting\n"));
	g_print(_("  --debug             Display debugging output\n"));
	g_print(_("  --debug-focus       Display debugging output for focus handling\n"));
	g_print(_("  --debug-xinerama    Split the display into fake xinerama screens\n"));
	g_print(_("\nPlease report bugs at %s\n"), PACKAGE_BUGREPORT);
}

static void remove_args(gint *argc, gchar **argv, gint index, gint num)
{
	gint i;
	for(i = index; i < *argc - num; ++i)
		argv[i] = argv[i + num];
	for(; i < *argc; ++i)
		argv[i] = NULL;
	*argc -= num;
}

static void run_startup_cmd(void)
{
	gchar **argv = NULL;
	GError *e = NULL;
	gboolean ok;
	if(!g_shell_parse_argv(startup_cmd, NULL, &argv, &e)) {
		g_message("Error parsing startup command: %s", e->message);
		g_error_free(e);
		e = NULL;
	}
	ok = g_spawn_async(NULL, argv, NULL, G_SPAWN_SEARCH_PATH | G_SPAWN_DO_NOT_REAP_CHILD, NULL, NULL, NULL, &e);
	if(!g_shell_parse_argv(startup_cmd, NULL, &argv, &e)) {
		g_message("Error launching startup command: %s", e->message);
		g_error_free(e);
		e = NULL;
	}
}

static void parse_env(void)
{
	const gchar *id;
	g_unsetenv("DESKTOP_STARTUP_ID");
	id = g_getenv("DESKTOP_AUTOSTART_ID");
	if(id) {
		g_unsetenv("DESKTOP_AUTOSTART_ID");
	}
}

static void parse_args(gint *argc, gchar **argv)
{
	gint i;
	for(i = 1; i < *argc; ++i) {
		if(!strcmp(argv[i], "--version")) {
			print_version();
			exit(0);
		} else if(!strcmp(argv[i], "--help")) {
			print_help();
			exit(0);
		} else if(!strcmp(argv[i], "--g-fatal-warnings")) {
			g_log_set_always_fatal(G_LOG_LEVEL_CRITICAL);
		} else if(!strcmp(argv[i], "--replace")) {
			wm_replace = TRUE;
			remove_args(argc, argv, i, 1);
			--i;
		} else if(!strcmp(argv[i], "--sync")) {
			xsync = TRUE;
		} else if(!strcmp(argv[i], "--startup")) {
			if(i == *argc - 1)
				g_printerr(_("%s requires an argument\n"), "--startup");
			else {
				startup_cmd = argv[i + 1];
				remove_args(argc, argv, i, 2);
				--i;
				wm_debug("--startup %s", startup_cmd);
			}
		} else if(!strcmp(argv[i], "--debug")) {
			wm_debug_enable(WM_DEBUG_NORMAL, TRUE);
			wm_debug_enable(WM_DEBUG_APP_BUGS, TRUE);
		} else if(!strcmp(argv[i], "--debug-focus")) {
			wm_debug_enable(WM_DEBUG_FOCUS, TRUE);
		} else if(!strcmp(argv[i], "--debug-xinerama")) {
			wm_debug_xinerama = TRUE;
		} else if(!strcmp(argv[i], "--config-file")) {
			if(i == *argc - 1)
				g_printerr(_("%s requires an argument\n"), "--config-file");
			else {
				config_file = argv[i + 1];
				++i;
				wm_debug("--config-file %s", config_file);
			}
		} else {
			gchar *err = g_strdup_printf(_("Invalid command line argument \"%s\"\n"), argv[i]);
			exit_with_error(err);
		}
	}
}

static Cursor load_cursor(const gchar *name, guint fontval)
{
	Cursor c = None;
	c = XcursorLibraryLoadCursor(t_display, name);
	if(c == None)
		c = XCreateFontCursor(t_display, fontval);
	return c;
}

void wm_restart_other(const gchar *path)
{
	restart_path = g_strdup(path);
	wm_restart();
}

void wm_restart(void)
{
	restart = TRUE;
	wm_exit(0);
}

void wm_exit(gint code)
{
	exitcode = code;
	g_main_loop_quit(main_loop);
}

void wm_exit_replace(void)
{
	exitcode = 0;
	being_replaced = TRUE;
	g_main_loop_quit(main_loop);
}

Cursor wm_cursor(enum wm_cursor cursor)
{
	g_assert(cursor < WM_NUM_CURSORS);
	return cursors[cursor];
}

enum wm_state wm_state(void)
{
	return state;
}

void wm_set_state(enum wm_state s)
{
	state = s;
}
